/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import { AstTreeUtils, ts, ArkMethod, ArkFile, ArkAssignStmt, Type, FieldSignature } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from '../../model/Defects';
import { ClassMatcher, FileMatcher, MatcherCallback, MatcherTypes, MethodMatcher } from "../../matcher/Matchers";
import { Utils } from '../../Index';
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from '../../model/Fix';
const defaultOptions: Options = {
    allowSingleExtends: false
}
interface Options {
    allowSingleExtends?: boolean
}
interface lineColumnInfo {
    line: number,
    character: number
}
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoEmptyInterfaceCheck');
const gmetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/no-empty-interface-check.md", // TODO: support url
    description: "Disallow the declaration of empty interfaces."
};
export class NoEmptyInterfaceCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gmetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    private classMatcher: ClassMatcher = {
        file: [this.fileMatcher],
        matcherType: MatcherTypes.CLASS
    };
    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        class: [this.classMatcher]
    };
    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [matchBuildCb];
    }
    private issueMap: Map<string, IssueReport> = new Map();
    public check = (arkFile: ArkFile) => {
        // 将规则选项转换为Options类型
        let options: Options;
        if (this.rule && this.rule.option.length > 0) {
            options = this.rule.option[0] as Options;
        } else {
            options = defaultOptions;
        }
        this.checkInterfacesInFile(arkFile, this.metaData);
        if (options.allowSingleExtends == false) {
            this.reportSortedIssues();
        }
    }

    private checkInterfacesInFile(file: ArkFile, metaData: BaseMetaData): lineColumnInfo[] {
        const code = file.getCode();
        const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
        const emptyInterfaces: lineColumnInfo[] = [];
        function isEmptyInterface(node: ts.InterfaceDeclaration): boolean {
            // 检查当前接口是否有直接成员
            if (node.members.length > 0) {
                return false;
            }

            // 检查接口的基类型（如果有的话）
            const baseTypes = node.heritageClauses?.map(clause => clause.types).flat() || [];
            if (baseTypes.length == 1) {
                return true;
            } else {
                for (const baseType of baseTypes) {
                    // 获取基类型的实体名称（例如，另一个接口的名称）
                    const baseTypeName = baseType.expression.getText();
                    const baseTypeNode = findInterfaceNodeByName(baseTypeName, node.getSourceFile());
                    if (baseTypeNode) {
                        // 递归检查基类型接口是否为空
                        if (!isEmptyInterface(baseTypeNode)) {
                            return false;
                        }
                    }
                }
            }
            // 如果没有直接成员且所有基类型接口都为空，则当前接口为空
            return true;
        }
        // 辅助函数：在给定源文件中查找具有指定名称的接口节点
        function findInterfaceNodeByName(name: string, sourceFile: ts.SourceFile): ts.InterfaceDeclaration | undefined {
            function traverse(node: ts.Node): ts.InterfaceDeclaration | undefined {
                if (ts.isInterfaceDeclaration(node) && node.name.getText() === name) {
                    return node;
                }
                for (const childNode of node.getChildren()) {
                    const result = traverse(childNode);
                    if (result) {
                        return result;
                    }
                }
                return undefined;
            }
            return traverse(sourceFile);
        }
        const traverse = (node: ts.Node): void => {
            if (ts.isInterfaceDeclaration(node)) {
                if (isEmptyInterface(node)) {
                    if (node.heritageClauses) {
                        metaData.description = 'An interface declaring no members is equivalent to its supertype.';
                        const name = node.name;
                        const nodeText = node.getText();
                        const pos = node.getStart();
                        const end = node.getEnd();
                        const text = name.getText();
                        let fixText;
                        const baseTypes = node.heritageClauses?.map(clause => clause.types).flat() || [];
                        for (const baseType of baseTypes) {
                            const baseT = baseType.getText();
                            if (nodeText.startsWith('export')) {
                                fixText = `export type ${text} = ${baseT}`;
                            } else {
                                if (nodeText.includes(`${text}<T>`)) {
                                    fixText = `type ${text}<T> = ${baseT}`;
                                } else {
                                    fixText = `type ${text} = ${baseT}`;
                                }
                            }
                        }
                        const start = name.getStart();
                        const position = ts.getLineAndCharacterOfPosition(sourceFile, start);
                        const line = position.line + 1;
                        const character = position.character + 1;
                        emptyInterfaces.push({ line, character });
                        const defect = this.addIssueReport(file, metaData, { line: line, character: character });
                        if (fixText) {
                            let fix: RuleFix = this.ruleFix(pos, end, fixText);
                            if (defect) {
                                this.issueMap.set(defect.fixKey, { defect, fix });
                            }
                        }
                    }
                    else {
                        metaData.description = 'An empty interface is equivalent to `{}`.';
                        const name = node.name;
                        const nodeName = node.getText();
                        const pos = node.getStart();
                        const end = node.getEnd();
                        const text = name.getText();
                        const fixText = nodeName;
                        const start = name.getStart();
                        const position = ts.getLineAndCharacterOfPosition(sourceFile, start);
                        const line = position.line + 1;
                        const character = position.character + 1;
                        emptyInterfaces.push({ line, character });
                        const defect = this.addIssueReport(file, metaData, { line: line, character: character });
                        let fix: RuleFix = this.ruleFix(pos, end, fixText);
                        if (defect) {
                            this.issueMap.set(defect.fixKey, { defect, fix });
                        }
                    }
                }
            }
            ts.forEachChild(node, childNode => traverse(childNode));
        }
        traverse(sourceFile);
        return emptyInterfaces;
    }
    private ruleFix(pos: number, end: number, fixText: string): RuleFix {
        return { range: [pos, end], text: fixText };
    }
    private reportSortedIssues(): void {
        if (this.issueMap.size === 0) {
            return;
        }

        const sortedIssues = Array.from(this.issueMap.entries())
            .sort(([keyA], [keyB]) => Utils.sortByLineAndColumn(keyA, keyB));

        this.issues = [];

        sortedIssues.forEach(([_, issue]) => {
            RuleListUtil.push(issue.defect);
            this.issues.push(issue);
        });
    }
    private addIssueReport(arkFile: ArkFile, metaData: BaseMetaData, lineAndColumn: lineColumnInfo) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const warnInfo = this.getLineAndColumn(arkFile, lineAndColumn);
        metaData.description = metaData.description.padEnd(130);
        if (warnInfo) {
            const fixKey = warnInfo.line + '%' + warnInfo.startCol + '%' + this.rule.ruleId;
            const mergeKey = warnInfo.filePath + '%' + fixKey + '%' + metaData.description + this.rule.ruleId;
            const defect = new Defects(warnInfo.line, warnInfo.startCol, metaData.description, severity, this.rule.ruleId,
                mergeKey, metaData.ruleDocPath, true, false, false, fixKey);
            this.defects.push(defect);
            return defect;
        }
    }
    private getLineAndColumn(arkfile: ArkFile, lineColumn: lineColumnInfo) {
        if (arkfile) {
            const originPath = arkfile.getFilePath();
            return { line: lineColumn.line, startCol: lineColumn.character, filePath: originPath };
        } else {
            logger.debug('arkFile is null');
        }
        return null;
    }
}